# ==============================
# _*_ coding:utf-8 _*_
# _*_ by:zizle     _*_
# _*_ at:20230417  _*_
# ==============================
import pathlib
import datetime
import re
import requests
from fastapi import APIRouter, Body, Depends, UploadFile, Form, BackgroundTasks
from fastapi.requests import Request
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel, constr, validator

from response import APIResponse
from db_utils import BaseDatabaseConnection, create_sql
from constant.user import UserSigninType, UserType
from utils import datetime_utils
from utils.jwt_token import Token, TokenPayload
from utils.encrypt import Encrypt
from utils.hutool_utils import StringUtils
from settings import STATIC_HOST


""" models """


class RegisterBodyModel(BaseModel):
    nickname: str
    phone: str
    service: str  # 客户经理
    has_futures_account: bool  # 是否公司开户
    is_employee: bool  # 是否员工
    password1: str
    password2: str
    username: str = ''
    company: str = ''
    job: str = ''
    invitation_code: str = ''

    @validator('phone')
    def validate_phone(cls, value):
        if re.match('^1[3-9]\d{9}$', value):
            return value
        raise ValueError('请输入正确的手机号!')


# 登录参数
class LoginBodyModel(BaseModel):
    account: str                 # 账户
    password: str                # 密码
    has_encrypted: bool = False  # 加密的


# 修改参数
class ModifyUserItem(BaseModel):
    avatar: constr(max_length=500) = None
    nickname: constr(max_length=10) = None
    company: constr(max_length=30) = None
    job: constr(max_length=30) = None


# 修改密码
class ModifyPassword(BaseModel):
    password: constr(min_length=6, max_length=20)
    new_password1: constr(min_length=6, max_length=20)
    new_password2: constr(min_length=6, max_length=20)


""" API Classes """


class AuthUser:
    def log_user_login2(self, user_id, ip_str: str):
        if ip_str == '127.0.0.1' or len(str(ip_str)) < 7:
            return
        ip_info_url = 'https://qifu-api.baidubce.com/ip/geo/v1/district'
        params = {'ip': ip_str }
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203'}
        country = ''
        province = ''
        city = ''
        district = ''
        longitude = None
        latitude = None
        isp_name = ''
        try:
            r = requests.get(ip_info_url, params=params, headers=headers)
            info_resp = r.json()['data']
            country = info_resp['country']
            province = info_resp['prov']
            city = info_resp['city']
            district = info_resp['district']
            longitude = info_resp['lng']
            latitude = info_resp['lat']
            isp_name = info_resp['isp']
        except Exception as e:
            folder = pathlib.Path('log/Net')
            if not folder.exists():
                folder.mkdir(parents=True)
            log_time = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
            with open(folder.joinpath(f'{log_time}.log'), 'a+', encoding='utf8') as lf:
                lf.write(f'获取ip= {ip_str} 信息失败:{e}')
        # 写入数据库
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute("""
                INSERT INTO ruizhi_user_login(user_id,origin_ip,country,province,city,district,longitude,latitude,isp_name) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s);
            """, [user_id, ip_str, country, province, city, district, longitude, latitude, isp_name])

    def log_user_login(self, user_id: int, ip_str: str):
        # print(ip_str)
        if ip_str == '127.0.0.1':
            return
        ip_info_url = 'https://opendata.baidu.com/api.php'
        params = {'query': ip_str, 'co': '', 'resource_id': 6006, 'oe': 'utf8'}
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203'}
        location = ''
        isp_name = ''
        try:
            r = requests.get(ip_info_url, params=params, headers=headers)
            info_resp = r.json()
            ip_location = info_resp['data'][0]['location']
            location, isp_name = ip_location.split(' ')
        except Exception as e:
            folder = pathlib.Path('log/Net')
            if not folder.exists():
                folder.mkdir(parents=True)
            log_time = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
            with open(folder.joinpath(f'{log_time}.log'), 'a+', encoding='utf8') as lf:
                lf.write(f'获取ip= {ip_str} 信息失败:{e}')
        # 写入数据库
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute("INSERT INTO ruizhi_user_login(user_id,origin_ip,location,isp_name) VALUES (%s,%s,%s,%s);",
                                [user_id, ip_str, location, isp_name])

    def account_get_db_user(self, account):
        sql = "SELECT * FROM ruizhi_user WHERE (ssn=%s OR phone=%s) AND is_active=1 LIMIT 1;"
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute(sql, [account, account])
            user = conn.cursor.fetchone()
        return user

    def get_db_user(self, uid: int, signin_type: int = -1):
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute("SELECT * FROM ruizhi_user WHERE id=%s AND is_active=1 LIMIT 1;", [uid])
            user = conn.cursor.fetchone()
            if user:
                # 查询用户的权限板块
                user['access_sections'] = []  # 全板块
                user['access_all'] = True
                if user['user_type'] == UserType.REGISTER_USER or user['user_type'] in UserType.SECTION_AUTH_SETS:
                    conn.cursor.execute("SELECT id,sec_key FROM ruizhi_user_section WHERE user_id=%s AND expire_date>=CURDATE();", [uid])
                    access_sections = list(conn.cursor.fetchall())
                    user['access_sections'] = access_sections
                    user['access_all'] = False
                if signin_type > 0:  # 顺带签到
                    obj_activation = self.add_activation(db_conn=conn, user_id=uid, active_type=signin_type)
                    user['activation'] = obj_activation['remain']
                    user['signin_days'] = obj_activation['conn_days']
        return user

    # 增加活跃度
    def add_activation(self, db_conn: BaseDatabaseConnection, user_id: int, active_type: int):
        # 查询最新活跃度
        latest_activate_sql = """
            SELECT id,create_time,change_num,remain,cumulative,conn_days,total_conn_days FROM ruizhi_user_activation
            WHERE user_id=%s ORDER BY create_time DESC LIMIT 1;
        """
        # 查询当前类型的最新活跃记录
        type_activate_sql = """
            SELECT id,create_time,remain,conn_days,total_conn_days FROM ruizhi_user_activation
            WHERE user_id=%s AND active_type=%s ORDER BY 
            create_time DESC LIMIT 1;
        """
        db_conn.cursor.execute(latest_activate_sql, [user_id])
        latest_activate = db_conn.cursor.fetchone()
        db_conn.cursor.execute(type_activate_sql, [user_id, active_type])
        type_activate = db_conn.cursor.fetchone()

        if type_activate and type_activate['create_time'][0:10] == datetime_utils.now_yyyy_mm_dd():
            # print('今日{}已增加过活跃度'.format(UserSigninType.signin_type(t=active_type)))
            return {
                'remain': latest_activate['remain'] if latest_activate else 0,
                'conn_days': type_activate['conn_days'] if type_activate else 0
            }

        if latest_activate:
            conn_days = latest_activate['conn_days']
            total_conn_days = latest_activate['total_conn_days']
        else:
            conn_days = 0
            total_conn_days = 0

        add_activation = 0
        if active_type == UserSigninType.LOGIN:
            add_activation = 50  # 登录50活跃度
        if active_type == UserSigninType.SIGNIN:  # 签到
            yesterday = datetime_utils.interval_days(dt=datetime_utils.now_yyyy_mm_dd(), days=-1).strftime('%Y-%m-%d')
            if type_activate and type_activate['create_time'][0:10] == yesterday:  # 连续签到
                # 增加活跃度且设置连续天数
                conn_days = latest_activate['conn_days'] + 1 if latest_activate['conn_days'] < 7 else 1
                total_conn_days = latest_activate['total_conn_days'] + 1
            else:
                conn_days = 1
                total_conn_days = 1
            add_activation = UserSigninType.get_activation(conn_days)

        remain = latest_activate['remain'] + add_activation if latest_activate else add_activation
        cumulative = latest_activate['cumulative'] + add_activation if latest_activate else add_activation
        description = UserSigninType.signin_type(t=active_type)

        add_activate_sql = """
            INSERT INTO ruizhi_user_activation(user_id,active_type,description,change_num,remain,cumulative,
            conn_days,total_conn_days) VALUES (%s,%s,%s,%s,%s,%s,%s,%s);
        """
        db_conn.cursor.execute(add_activate_sql, [user_id, active_type, description, add_activation, remain, cumulative,
                                                  conn_days, total_conn_days])
        return {
            'remain': remain,
            'cumulative': cumulative,
            'conn_days': conn_days
        }

    # 注册
    def register(self, body: RegisterBodyModel):
        if body.password1 != body.password2:
            return APIResponse.validate_error(detail='两次输入密码不一致!')
        if len(body.password1) < 6 or len(body.password1) > 20:
            return APIResponse.validate_error(detail='密码为6-20位字符!')
        exist_user = self.account_get_db_user(account=body.phone)
        if exist_user:
            return APIResponse.validate_error(detail='该手机号的用户已存在，请不要重复申请!')
        # 将新用户写入数据库
        ssn = Encrypt.random_string(length=11, use_hash=True)
        password_hash = Encrypt.password_hash(password=body.password1)
        nickname = body.nickname if body.nickname else f'智者{ssn[:6]}'
        sql = """
            INSERT INTO ruizhi_user(ssn,phone,nickname,username,company,job,vip_expire,password,service,has_futures_account,
            user_type,probation_expire,accept_invitation,is_employee) 
            VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);
        """
        ut_code = 3
        probation_expire = datetime_utils.interval_days(datetime.date.today(), days=3, link='-', obj=False)
        invitation_code = body.invitation_code.strip() if body.invitation_code.strip() else None  # 这是写入受邀码的
        if body.invitation_code.strip():
            probation_expire = datetime_utils.interval_days(datetime.date.today(), days=7, link='-', obj=False)
        with BaseDatabaseConnection() as conn:
            if invitation_code:
                if len(invitation_code) != 4:
                    return APIResponse.validate_error(detail='邀请码应为4位数字，填写有误!')
                exist = self.invitation_code_exist(conn, invitation_code=invitation_code)
                if not exist:
                    return APIResponse.validate_error(detail='不正确的邀请码,请确认后提交!')
                self.add_probation_expire(conn, invitation_code=invitation_code, user_id=exist['id'])
            count = conn.cursor.execute(sql, [ssn, body.phone, nickname, body.username, body.company, body.job, '2023-01-01',
                                              password_hash, body.service, body.has_futures_account, ut_code, probation_expire,
                                              invitation_code, body.is_employee])
        return APIResponse.success(msg='申请成功!') if count else APIResponse.validate_error(detail='申请失败，请尝试重新发起!')

    def login(self, body: LoginBodyModel, request: Request, background_tasks: BackgroundTasks):
        user = self.account_get_db_user(account=body.account)
        if not user:
            return APIResponse.validate_error(detail='无效用户或登录信息错误!')
        # 验证密码
        if not Encrypt.password_verify(password_plain=body.password, password_hash=user['password']):
            return APIResponse.validate_error(detail='用户名或密码错误!')
        token = Token(payload={'uid': user['id'], 'utp': user['user_type']}).encode_token()
        # 写入用户登录的ip
        real_ip = request.headers.get('X-Real-IP')
        if real_ip:
            background_tasks.add_task(self.log_user_login2, user['id'], real_ip)
        return APIResponse.success(data={'token': token}, msg='登录成功!')

    def info(self, payload):
        user = self.get_db_user(uid=payload.user_id(), signin_type=UserSigninType.LOGIN)  # 登录签到，活跃度+50
        if not user:
            return APIResponse.user_inactive_error()
        avatar_url = f'{STATIC_HOST}{user["avatar"]}' if user['avatar'] else f'{STATIC_HOST}avatar/common.png'
        res_data = {
            'avatar': avatar_url,
            'nickname': user['nickname'],
            'vip_expire': user['vip_expire'],
            'username': user['username'],
            'is_vip': user['vip_expire'] >= datetime_utils.now_yyyy_mm_dd(),
            'activation': user['activation'],
            'signin_days': user['signin_days'],
            'ssn': user['ssn'],
            'phone': user['phone'],
            'company': user['company'],
            'job': user['job'],
            'user_type': user['user_type'],
            'probation_expired': True if user['probation_expire'] and user['probation_expire'] < datetime_utils.now_yyyy_mm_dd() else False,
            'invitation_code': user['invitation_code'],
            'access_sections': user['access_sections'],
            'access_all': user['access_all']
        }
        return APIResponse.success(data=res_data, msg='获取信息成功!')

    def modify_info(self, payload: TokenPayload, user_item: ModifyUserItem):
        user_dict = jsonable_encoder(user_item)
        modify_dict = {}
        for k in user_dict:
            if user_dict[k]:
                modify_dict[k] = user_dict[k]
        modify_dict = {k: user_dict[k].strip() for k in user_dict if user_dict[k]}
        if len(modify_dict) < 1:
            return APIResponse.validate_error(detail='没有检测到需要修改的信息!')
        update_columns_sql = create_sql(modify_dict)
        sql = "UPDATE ruizhi_user SET " + update_columns_sql + ' WHERE id=%(uid)s LIMIT 1;'
        modify_dict['uid'] = payload.user_id()
        if modify_dict.get('avatar'):
            modify_dict['avatar_url'] = modify_dict['avatar']
            modify_dict['avatar'] = modify_dict['avatar'].replace(STATIC_HOST, '')
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute(sql, modify_dict)

        return APIResponse.success(data=modify_dict, msg='修改成功!')

    def modify_password(self, payload: TokenPayload, password: ModifyPassword):
        if password.new_password1 != password.new_password2:
            return APIResponse.validate_error(detail='两次输入密码不一致!')
        user_obj = self.get_db_user(uid=payload.user_id())
        # 验证旧密码
        if not Encrypt.password_verify(password.password, user_obj['password']):
            return APIResponse.validate_error(detail='旧密码有误!')
        if password.new_password1 == password.password:
            return APIResponse.validate_error(detail='新密码与旧密码一致，无需修改!')
        # hash新密码
        password_hash = Encrypt.password_hash(password.new_password1)
        # 修改
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute("UPDATE ruizhi_user SET password=%s WHERE id=%s LIMIT 1;",
                                [password_hash, payload.user_id()])
        return APIResponse.success(msg='修改密码成功，请重新登录!')

    async def modify_avatar(self, payload: TokenPayload, avatar: UploadFile):

        return APIResponse.success()

    def refresh_token(self, payload: TokenPayload):
        token = Token(payload={'uid': payload.user_id(), 'utp': payload.user_type()}).encode_token()
        return APIResponse.success(data={'token': token}, msg='token刷新成功!')

    # 用户签到
    def signin(self, payload: TokenPayload):
        with BaseDatabaseConnection() as conn:
            obj_activation = self.add_activation(db_conn=conn, user_id=payload.user_id(), active_type=UserSigninType.SIGNIN)
        return APIResponse.success(data={
            'activation': obj_activation['remain'],
            'cumulative': obj_activation['cumulative'],
            'conn_days': obj_activation['conn_days']
        })

    # 签到信息
    def signin_info(self, payload: TokenPayload):
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute("""
                SELECT id,create_time,remain,conn_days FROM ruizhi_user_activation WHERE user_id=%s ORDER BY create_time DESC LIMIT 1;
            """, [payload.user_id()])
            ret = conn.cursor.fetchone()
            # 今日是否签到
            conn.cursor.execute("""
                SELECT id,create_time FROM ruizhi_user_activation WHERE user_id=%s AND active_type=%s ORDER BY create_time DESC LIMIT 1;
            """, [payload.user_id(), UserSigninType.SIGNIN])
            signin = conn.cursor.fetchone()
            if not signin:
                ret['today_signin'] = False
                ret['conn_days'] = 0
            else:
                ret['today_signin'] = signin['create_time'][0:10] == datetime_utils.now_yyyy_mm_dd()
                # 检测连签时间
                yesterday = datetime_utils.interval_days(dt=datetime_utils.now_yyyy_mm_dd(), days=-1).strftime('%Y-%m-%d')
                if signin['create_time'][0:10] != datetime_utils.now_yyyy_mm_dd() and signin['create_time'][0:10] != yesterday:
                    ret['conn_days'] = 0  # 断签了
                if ret['conn_days'] == 7:
                    ret['conn_days'] = 0
        return APIResponse.success(data=ret)

    def invitation_code_exist(self, conn, invitation_code):
        conn.cursor.execute('SELECT id,invitation_code FROM ruizhi_user WHERE invitation_code=%s LIMIT 1;', [invitation_code])
        exist = conn.cursor.fetchone()
        return exist

    def add_probation_expire(self, conn, invitation_code, user_id):
        sql = """
            UPDATE ruizhi_user SET probation_expire=(SELECT DATE_ADD(probation_expire, INTERVAL 10 DAY)) 
            WHERE probation_expire IS NOT NULL AND invitation_code=%s;
        """
        conn.cursor.execute(sql, [invitation_code])
        conn.cursor.execute("INSERT INTO ruizhi_user_event_log (event_log,user_id) VALUES (%s,%s);", ['邀请用户注册获得10天试用', user_id])

    def invitation_code(self, payload: TokenPayload):
        with BaseDatabaseConnection() as conn:
            conn.cursor.execute("SELECT id,invitation_code FROM ruizhi_user WHERE id=%s LIMIT 1;", [payload.user_id()])
            u_code = conn.cursor.fetchone()
            if u_code and u_code['invitation_code']:
                return APIResponse.success(data={'invitation_code': u_code['invitation_code']})
            # 没有邀请码
            code = StringUtils.random_string(length=4)
            count = None
            while not self.invitation_code_exist(conn, code) and not count:
                count = conn.cursor.execute('UPDATE ruizhi_user SET invitation_code=%s WHERE id=%s LIMIT 1;', [code, payload.user_id()])
        return APIResponse.success(data={'invitation_code': code})


""" Routes """
auth_api = APIRouter()


# 注册
@auth_api.post('/register/')
def auth_user_register(body: RegisterBodyModel = Body(...)):
    return AuthUser().register(body)


# 登录
@auth_api.post('/login/')
def auth_user_login(request: Request, background_tasks: BackgroundTasks, body: LoginBodyModel = Body(...)):
    return AuthUser().login(body, request=request, background_tasks=background_tasks)


# 刷新token
@auth_api.get('/refreshToken/')
def refresh_token(payload: TokenPayload = Depends(TokenPayload)):
    return AuthUser().refresh_token(payload=payload)


# 个人信息
@auth_api.get('/info/')
def auth_user_info(payload: TokenPayload = Depends(TokenPayload)):
    return AuthUser().info(payload)


# 修改个人信息
@auth_api.put('/info/')
def modify_user_info(payload: TokenPayload = Depends(TokenPayload), user: ModifyUserItem = Body(...)):
    return AuthUser().modify_info(payload=payload, user_item=user)


@auth_api.get('/invitationCode/')
def user_invitation_code(payload: TokenPayload = Depends(TokenPayload)):
    return AuthUser().invitation_code(payload=payload)


# 修改密码
@auth_api.put('/password/')
def modify_password(payload: TokenPayload = Depends(TokenPayload), password: ModifyPassword = Body(...)):
    return AuthUser().modify_password(payload=payload, password=password)


# 修改头像
@auth_api.put('/avatar/')
async def modify_avatar(payload: TokenPayload = Depends(TokenPayload), avatar: UploadFile = Form(...)):
    return AuthUser().modify_avatar(payload=payload, avatar=avatar)


# 签到信息
@auth_api.get('/signin/')
def user_signin(payload: TokenPayload = Depends(TokenPayload)):
    return AuthUser().signin_info(payload)


# 签到
@auth_api.post('/signin/')
def user_signin(payload: TokenPayload = Depends(TokenPayload)):
    return AuthUser().signin(payload)
